package Generador;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Hashtable;

import Elementos.Doble;
import Elementos.ListaMensajes;
import Elementos.Mensaje;
import Elementos.TablaTercetos;
import Elementos.Terceto;
import Lexico.EntradaTS;
import Lexico.TablaSimbolos;

public class Generator {
	private String direccionArchivo;
	private TablaSimbolos tSimbolos;
	private TablaTercetos tTercetos;
	private ListaMensajes mensajes;
	private String codigo;
	private Hashtable <Integer, Integer> saltos;			//agregado
	private final double LIMITESUMA = 1.7976931348623157 * Math.pow(10, 308);
	private final double cero= 0.0;
	private final double uno= 1.0;
	
	public Generator(String d, TablaSimbolos ts, TablaTercetos tt, ListaMensajes m) {
		this.direccionArchivo= this.generarDireccionArchivo(d);
		this.tSimbolos=ts;
		this.tTercetos= tt;
		this.mensajes= m;
		this.codigo="";
		saltos = new Hashtable<Integer, Integer>();
	}
	
	public String generarDireccionArchivo(String d){
		String dir="";
		int punto= d.indexOf(".");
		dir= d.substring(0, punto);
		return dir;
	}	
	
	public String getCodigo(){
		return this.codigo;
	}
	
	public void GenerarCodigo(){
		
		//agrego lo siguiente
		
		for(int i=1; i<=this.tTercetos.size(); i++){
			
			Terceto actual= this.tTercetos.getTerceto(i);
			if (actual.getOp()=="BF"){
				saltos.put(actual.gete1(), actual.getPuntero());
			}
			else if (actual.getOp()=="BI"){
				saltos.put(actual.gete1(), actual.getPuntero());
			}
			else if (actual.getOp()=="call"){
				saltos.put(actual.gete1(), actual.getPuntero());
			}
		}
		
		//hasta aca
		
		if(!this.mensajes.sinErrores()){
			this.mensajes.add(new Mensaje(Mensaje.AVISO, "El codigo contiene erroes. No se compilo. ", 0));			
		}
		else{    //Genera el codigo.
			String total="";
			total+=".586"+'\n';
			//total+="option casemap :none\n";
			total+=".model flat, stdcall"+'\n';
			total+=".STACK 200h"+'\n';
			total+="option casemap :none"+'\n';
			total+="include \\masm32\\include\\windows.inc"+'\n';
			total+="include \\masm32\\include\\kernel32.inc"+'\n';
			total+="include \\masm32\\include\\user32.inc"+'\n';
			total+="includelib \\masm32\\lib\\kernel32.lib"+'\n';
			total+="includelib \\masm32\\lib\\user32.lib"+'\n';
			//total+="include\\masm32\\include\\msvcrt.inc\n";
			//total+="includelib\\masm32\\lib\\msvcrt.lib\n";
			total+=".data\n";
			total+=GenerarVariables();
			total+=".code\n";
			total+="start:\n";
			total+="JMP MAIN \n"; 					//agregado
			total+=GenerarCodigoMedio();
			total+="invoke ExitProcess, 0\n";
			total+=GenerarCodigoErrores();
			total+="end start\n";
			//todo el codigo a la variable			
			this.codigo=total;
			GenerarArchivo();			
		}		
	}
	
	private String GenerarVariables(){
		String resultado="";
		
		//Variables para los mensajes especiales
		resultado+="MsgErrorOverflow DB \"ERROR! Resultado de Suma Genero Overflow.\", 0\n";
		resultado+="MsgDivisionPorCero DB \"ERROR! Se intento dividir por Cero.\", 0\n";
		resultado+="MsgExito DB \"El Programa Finalizo Correctamente!!!!.\", 0\n";
		resultado+="@limite DQ "+ LIMITESUMA + '\n' ;
		resultado+="@cero DQ "+ cero + '\n' ;
		resultado+="@uno DQ "+ uno + '\n' ;
		
		for (int i =1; i<= this.tSimbolos.size();i++){
			EntradaTS actual = this.tSimbolos.getSimbolo(i);
			switch(actual.getTipo()){
				case "double": 
						Doble d= (Doble) actual.getObjeto();
						resultado+= "_"+i + " DQ " + d.getValor()+"\n";
	
					break;
				case "identificador": 
					resultado+= "_"+i + " DQ " + "? " +"\n";
				break;
				//case "cadena": 
					//resultado+="_"+i + " DB  \""+ actual.getObjeto().toString() +"\", 0 \n";
				//break;
				case "funcion": break;			
				
			}
		}
		
		for(int i=1; i<=this.tTercetos.size(); i++){
			Terceto actual= this.tTercetos.getTerceto(i);
			if ((actual.getOp().equals("+"))||(actual.getOp().equals("-"))||(actual.getOp().equals("*"))||(actual.getOp().equals("/"))||(actual.getOp().equals("call"))||(actual.getOp().equals("it"))){
				resultado+= "@aux"+actual.getPuntero()+" DD ?\n"; 
			}
			if (actual.getOp().equals("print")){
				resultado+= "MensajeImprimir"+actual.getPuntero()+" DB \""+tSimbolos.getSimbolo(actual.gete1()).getObjeto()+"\", 0\n"; 
			}
			
		}		
		return resultado;
	}
	
	private String GenerarCodigoMedio(){
		String resultado="";
		for(int i=1; i<= this.tTercetos.size(); i++){
			
			//agrego lo siguiente
			
			
			if ((saltos.containsKey(i))&&(tTercetos.getTerceto(i).getOp()!="main")){
				resultado+="SALTO"+i+": ";
			}
			
			//hasta aca
			
			resultado= resultado + generarOperacion(this.tTercetos.getTerceto(i));
		}					
		return resultado;
	}
	
	
	public String generarOperacion(Terceto actual){
		String resultado="";
		switch (actual.getOp()){
			case "+":     resultado= suma(actual);  break;
			case "-":     resultado= resta(actual); break;
			case "*":     resultado= multiplicacion(actual); break;
			case "/":     resultado= division(actual); break;
			case "=":     resultado= asignacion(actual); break;
			case "!=":    resultado= comparacion(actual); break;
			case "==":    resultado= comparacion(actual); break;
			case ">":     resultado= comparacion(actual); break ; 
			case ">=":    resultado= comparacion(actual); break;
			case "<":     resultado= comparacion(actual); break;
			case "<=":    resultado= comparacion(actual); break;
			case "print": resultado= print(actual); break;
			case "BF":    resultado= BF(actual); break;
			case "BI":    resultado= BI(actual); break;
			case "call":  resultado= call(actual); break;
			case "ret":   resultado= ret(actual); break;	
			case "end":   resultado= fin(actual); break;
			case "main":  resultado= generar_main(actual); break; 
			case "inc":  resultado= inc(actual); break; 
			case "it":  resultado= it(actual); break; 
		}		
		return resultado;
	}	
	
	public String it(Terceto actual){
		String resultado="";	
		
		String e1="";
		if(actual.getE1esSimbolo())
			e1="_"+actual.gete1();
		else
			e1="@aux"+actual.gete1();
		
		resultado+="FLD "+e1+"\n";
		resultado+="FSTP @aux" +actual.getPuntero()+ "\n";
		return resultado;
	}
	
	public String inc(Terceto actual){
		String resultado="";	
		
		String e1="";
		if(actual.getE1esSimbolo())
			e1="_"+actual.gete1();
		else
			e1="@aux"+actual.gete1();
		
		resultado+= "FLD " + e1 + '\n';
		resultado+= "FLD @uno" + '\n';

		resultado+= "FADD"  + '\n';
		resultado+= "FSTP " + e1 + '\n';
		return resultado;
	}
	
	
	//MATI-----------------------------------------------------------------------------------------------------------------
	
	public String BF(Terceto actual){
		String resultado="";
		String op = tTercetos.getTerceto(actual.getPuntero()-1).getOp();
		
		if (tTercetos.getTerceto(actual.gete1()).getOp()=="main"){
			
			switch(op){ 
	        case "<" : 															//CAMBIAR LOS JUMPS? 	
	            resultado+="JB MAIN"+"\n";
	            break;
	        case ">" :  
	        	resultado+="JA MAIN"+"\n";
	            break;
	        case "==" : 
	        	resultado+="JNE MAIN"+"\n";
	            break;
	        case "<=" :
	        	resultado+="JBE MAIN"+"\n";
	            break;
	        case ">=" :
	        	resultado+="JAE MAIN"+"\n";
	            break;
	        case "!=" : 
	        	resultado+="JE MAIN"+"\n";
	            break;
		}
		}
		else{
		
		switch(op){ 
        case "<" : 															//CAMBIAR LOS JUMPS? 	
            resultado+="JBE SALTO"+actual.gete1()+"\n";
            break;
        case ">" :  
        	resultado+="JAE SALTO"+actual.gete1()+"\n";
            break;
        case "==" : 
        	resultado+="JNE SALTO"+actual.gete1()+"\n";
            break;
        case "<=" :
        	resultado+="JB SALTO"+actual.gete1()+"\n";
            break;
        case ">=" :
        	resultado+="JA SALTO"+actual.gete1()+"\n";
            break;
        case "!=" : 
        	resultado+="JE SALTO"+actual.gete1()+"\n";
            break;
            
		}
		
		}
		return resultado;
	}
	
	public String BI(Terceto actual){
		String resultado="";
		resultado+="JMP SALTO"+actual.gete1()+"\n";
		return resultado;
	}
	
	public String generar_main(Terceto actual){
		String resultado="";
		resultado+="MAIN: NOP"+'\n';
		return resultado;
	}
	
	public String call(Terceto actual){								//arreglar
		String resultado="";
		resultado+="CALL SALTO"+actual.gete1()+"\n";
		
		resultado+="FLD _"+actual.gete2()+"\n";
		resultado+="FSTP @aux" +actual.getPuntero()+ "\n";
		

		return resultado;
	}
	
	public String ret(Terceto actual){
		String resultado="";
		if (actual.getE2esSimbolo()){
			resultado+="FLD _"+actual.gete2()+"\n";}
		else {resultado+="FLD @aux"+actual.gete2()+"\n";}
		
		resultado+="FSTP _" +actual.gete1()+ "\n";

		resultado+="RET" + '\n';
		return resultado;
	}
		
		/*String op1 = ObtenerOperando(t.getArg2());
		String op2 = ObtenerOperando(t.getArg3()); 
		String reg  = GetRegistro();
		codAss+="MOV "+reg+", "+op1+"\n";
		codAss+="CMP "+reg+", "+op2+"\n";
		
		*/
	
	//FRAN-----------------------------------------------------------------------------------------------------------------
	public String asignacion(Terceto actual){
		String resultado="";	
		
		String e1="";
		if(actual.getE1esSimbolo())
			e1="_"+actual.gete1();
		else
			e1="@aux"+actual.gete1();
		String e2="";
		if(actual.getE2esSimbolo())
			e2="_"+actual.gete2();
		else
			e2="@aux"+actual.gete2();
		
		resultado+="FLD "+e2+"\n";
		resultado+="FSTP " +e1+ "\n";
		return resultado;
	}
	
	public String suma(Terceto actual){
		String resultado="";	
		
		String e1="";
		if(actual.getE1esSimbolo())
			e1="_"+actual.gete1();
		else
			e1="@aux"+actual.gete1();
		String e2="";
		if(actual.getE2esSimbolo())
			e2="_"+actual.gete2();
		else
			e2="@aux"+actual.gete2();
		
		resultado+= "FLD " + e1 + '\n';
		resultado+= "FLD " + e2 + '\n';
		//chekear overflow (misterio)
		//resultado+= "JO ERROOVERFLOW"+'\n';

		resultado+= "FADD"  + '\n';
		resultado+= "FSTP @aux" + actual.getPuntero() + '\n';
		//check
		resultado+= "FLD @aux" + actual.getPuntero() + '\n';
		resultado+= "FCOM @limite"+ '\n';
		resultado+= "FSTSW AX" + '\n';
		resultado+= "FWAIT" + '\n';
		resultado+= "SAHF"+ '\n';
		resultado+= "JA ERROOVERFLOW" + '\n';
		return resultado;
	}
	
	public String resta(Terceto actual){
		String resultado="";
		
		String e1="";
		if(actual.getE1esSimbolo())
			e1="_"+actual.gete1();
		else
			e1="@aux"+actual.gete1();
		String e2="";
		if(actual.getE2esSimbolo())
			e2="_"+actual.gete2();
		else
			e2="@aux"+actual.gete2();

		resultado+= "FLD " + e1 + '\n';
		resultado+= "FLD " + e2 + '\n';
		resultado+= "FSUB "+ '\n';
		resultado+= "FSTP @aux" + actual.getPuntero() + '\n';
		
		
		return resultado;
	}
	
	public String multiplicacion(Terceto actual){
		String resultado="";
		
		String e1="";
		if(actual.getE1esSimbolo())
			e1="_"+actual.gete1();
		else
			e1="@aux"+actual.gete1();
		String e2="";
		if(actual.getE2esSimbolo())
			e2="_"+actual.gete2();
		else
			e2="@aux"+actual.gete2();
		
		resultado+= "FLD " + e1 + '\n';
		resultado+= "FLD " + e2 + '\n';
		resultado+= "FMUL"+ '\n';
		resultado+= "FSTP @aux" + actual.getPuntero() + '\n';
		
		return resultado;
	}
	
	public String division(Terceto actual){
		String resultado="";
		
		String e1="";
		if(actual.getE1esSimbolo())
			e1="_"+actual.gete1();
		else
			e1="@aux"+actual.gete1();
		String e2="";
		if(actual.getE2esSimbolo())
			e2="_"+actual.gete2();
		else
			e2="@aux"+actual.gete2();		
		
		//chekear division cero
		//resultado+= "JZ DIVISIONCERO" + '\n';
		//No es cero continua
		
		resultado+= "FLD " + e2 + '\n';
		resultado+= "FCOM @cero"+ '\n';
		resultado+= "FSTSW AX" + '\n';
		resultado+= "FWAIT" + '\n';
		resultado+= "SAHF"+ '\n';
		resultado+= "JE DIVISIONCERO" + '\n';
		
		
		resultado+= "FLD " + e1 + '\n';
		resultado+= "FLD " + e2 + '\n';
		resultado+= "FDIV"+ '\n';
		resultado+= "FSTP @aux" + actual.getPuntero() + '\n';
		
		return resultado;
	}
	
	public String comparacion(Terceto actual){
		String resultado="";
		
		String e1="";
		if(actual.getE1esSimbolo())
			e1="_"+actual.gete1();
		else
			e1="@aux"+actual.gete1();
		String e2="";
		if(actual.getE2esSimbolo())
			e2="_"+actual.gete2();
		else
			e2="@aux"+actual.gete2();
		
		resultado+= "FLD " + e1 + '\n';
		resultado+= "FLD " + e2 + '\n';
		resultado+= "FCOMPP"+ '\n';
		resultado+= "FSTSW AX"+ '\n';
		resultado+= "SAHF"+ '\n';
		return resultado;
	}
	
	public String print(Terceto actual){
		String resultado="";
		resultado+= "invoke MessageBox, NULL, addr MensajeImprimir"+actual.getPuntero()+", addr MensajeImprimir"+actual.getPuntero()+", MB_OK\n";
		return resultado;
	}
	
	private String GenerarCodigoErrores(){
		String resultado="";
		//ERROR OVERFLOW 
		resultado+="ERROOVERFLOW:\n";
		resultado+="invoke MessageBox, NULL, addr MsgErrorOverflow, addr MsgErrorOverflow, MB_OK\n";
		resultado+="invoke ExitProcess, 0\n";
		//ERROR DIVISIONCERO 
		resultado+="DIVISIONCERO:\n";
		resultado+="invoke MessageBox, NULL, addr MsgDivisionPorCero, addr MsgDivisionPorCero, MB_OK\n";
		resultado+="invoke ExitProcess, 0\n";
		//FINALIZACION CON EXITOOOOO
		resultado+="EXITO:\n";
		resultado+="invoke MessageBox, NULL, addr MsgExito, addr MsgExito, MB_OK\n";
		resultado+="invoke ExitProcess, 0\n";
		return resultado;
	}
	
	private String fin(Terceto Actual){
		String resultado="";
		resultado+="JMP EXITO\n";
		return resultado;
	}
	
	private void GenerarArchivo(){
		
		File arch = new File(this.direccionArchivo+".asm");
		try {
			//Genera .asm
			PrintWriter p = new PrintWriter(new FileWriter(arch));
			p.print(this.codigo);			
			p.close();		
			
			//Genera .exe
			String comc = "cmd /c .\\masm32\\bin\\ml /SUBSYSTEM:CONSOLE /c /Zd /coff " + this.direccionArchivo + ".asm";
	        Process ptasm32;
			ptasm32 = Runtime.getRuntime().exec(comc);
			InputStream is = ptasm32.getInputStream();
		    BufferedReader br = new BufferedReader(new InputStreamReader(is));
		    String aux = br.readLine();
		    while (aux!=null){
		    	System.out.println(aux);
		        aux = br.readLine();
		    }        
		    //genera .obj
	        String coml = "cmd /c \\masm32\\bin\\Link /SUBSYSTEM:CONSOLE " + this.direccionArchivo + ".obj";
	        Process ptlink32;
			ptlink32 = Runtime.getRuntime().exec(coml);
			InputStream is2 = ptlink32.getInputStream();
		    BufferedReader br2 = new BufferedReader(new InputStreamReader(is2));
		    String aux2 = br2.readLine();
		    while (aux2!=null){
		    	System.out.println(aux2);
		    	aux2 = br2.readLine();
		    }
		    //ejecuta .exe
		   /* Runtime aplicacion = Runtime.getRuntime();
	        try {
	            aplicacion.exec(this.direccionArchivo + ".exe"); 
	        } catch (Exception e){
	            System.out.println("No se pudo generar ejecutable pero si asm.");
	        }			*/
		}catch (IOException e1) {
			System.out.println("Error al guardar archivo asm.");
		}		
	
	}
}

	